﻿//--------------------------------------------------------------------------
// 
//  Copyright (c) Chili Software.  All rights reserved. 
// 
//  File: FirebirdCommandBuilder.cs
//
//  Description: Creates SQL commands for Firebird.
// 
//--------------------------------------------------------------------------

using System;
using System.Reflection;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Text;
using Chili.Opf3.Query;
using Chili.Opf3.Query.OPathParser;
using FirebirdSql.Data.FirebirdClient;
using Chili.Opf3.Mapping;

namespace Chili.Opf3.Storages.Firebird
{
	/// <summary>
	/// Class that converts
	/// <see cref="Opf3.Query.ObjectExpression">ObjectExpression</see> classes to storage
	/// dependent <see cref="Opf3.Query.CompiledQuery">CompiledQuery</see> classes.
	/// </summary>
	internal sealed class FirebirdCommandBuilder : SqlCommandBuilderBase
	{
		/// <summary>
		/// Creates a new instance of the <see cref="FirebirdCommandBuilder">FirebirdCommandBuilder Class</see>.
		/// </summary>
		internal FirebirdCommandBuilder() : base(true)
		{
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="storage"></param>
		/// <param name="persistentAttribute"></param>
		/// <param name="fieldAttribute"></param>
		/// <returns></returns>
		private object GetAutoNumberValue(FirebirdStorage storage, PersistentAttribute persistentAttribute, FieldAttribute fieldAttribute)
		{
			// Get the name of the generator.
			string generator = string.Format(storage.GeneratorFormat, persistentAttribute.Entity, fieldAttribute.FieldName);

			// Create a new connection and command object to query the next value for the autoincrement.
			FbConnection connection = new FbConnection(storage.ConnectionString);

			try
			{
				connection.Open();
				FbCommand cmd = new FbCommand(string.Format("SELECT GEN_ID({0}, 1) FROM RDB$DATABASE", generator), connection);
				return cmd.ExecuteScalar();
			}
			finally
			{
				connection.Close();
			}
		}

		#region BuildInsertQuery

		/// <summary>
		/// Creates the <see cref="Opf3.Query.CompiledQuery">CompildQuery</see> that contains a storage dependent 
		/// update query by using the <see cref="Opf3.Query.PersistentObjectExpression">PersistentObjectExpression</see> given.
		/// </summary>
		/// <param name="poe">
		/// 	<see cref="Opf3.Query.PersistentObjectExpression">PersistentObjectExpression</see> that is converted
		/// to a <see cref="Opf3.Query.CompiledQuery">CompildQuery</see>.
		/// </param>
		/// <param name="sqlStorageBase">The storage that the query is compiled for.</param>
		/// <exception cref="ArgumentNullException">PersistentObjectExpression is null.</exception>
		/// <exception cref="ArgumentNullException">SqlStorageBase is null.</exception>
		protected override CompiledQuery BuildInsertQuery(SqlStorageBase sqlStorageBase, PersistentObjectExpression poe)
		{
			if (sqlStorageBase == null)
				throw new ArgumentNullException("sqlStorageBase");
			if (poe == null)
				throw new ArgumentNullException("poe");

			// Create the command.
			IDbCommand command = sqlStorageBase.CreateCommand();
			CompiledQuery compiledQuery = new CompiledQuery(command, QueryTypes.Insert);
			// Set the timeout for the query.
			compiledQuery.Timeout = poe.Timeout;

			// Create the string builder for the query.
			StringBuilder fields = new StringBuilder();
			StringBuilder parameters = new StringBuilder();

			// Get the persistent, the mapping and the list of members.
			object persistent = poe.Persistent;
			IPartialPersist partial = persistent as IPartialPersist;
			TypeMapping mapping = TypeMapping.GetTypeMapping(persistent);
			IMemberInfoCollection members = mapping.Members;

			for (int i = 0; i < members.Count; i++)
			{
				IMemberInfo mi = members[i];
				FieldAttribute fa = mi.FieldAttribute;

				// Skip if the member should not be inserted.
				if ((fa.PersistBehaviours & PersistBehaviours.DontInsert) != 0)
					continue;
				// Skip members that are not to persist.
				if (partial != null && !partial.IsMemberToPersist(mi.Name, PersistentOperations.Insert))
					continue;

				object value = null;
				// Check if the condition is an autonumber. This must be always performed also if the value of the 
				// parameter is null. It is never skipped.
				if (fa.AutoNumber)
				{
					// Get the value for the autonumber.
					value = GetAutoNumberValue((FirebirdStorage)sqlStorageBase, mapping.PersistentAttribute, mi.FieldAttribute);

					// Set the persistent attribute for the autonumbers.
					if (compiledQuery.AutoNumberItems == null)
						compiledQuery.AutoNumberItems = new AutoNumberItems(mapping.PersistentAttribute);
					// Add the field attribute to the fields.
					compiledQuery.AutoNumberItems.Add(new AutoNumberItem(fa, value));
				}
				else
				{
					value = mi.GetValue(persistent);
					// Ignore null values. They are not used in the insert.
					if (value == null)
						continue;
				}

				// Create the parameter.
				string paramName = "pn" + i;
				IDataParameter dataParameter = sqlStorageBase.CreateParameter(i, ref paramName, value, mi.MemberType);

				if (fields.Length == 0)
				{
					fields.Append(sqlStorageBase.GetValidFieldName(fa.FieldName));
					parameters.Append(paramName);
				}
				else
				{
					fields.AppendFormat(", {0}", sqlStorageBase.GetValidFieldName(fa.FieldName));
					parameters.AppendFormat(", {0}", paramName);
				}

				// Add the parameter to the command.
				command.Parameters.Add(dataParameter);
			}

			// Create the string builder that holds the final query.
			command.CommandText = string.Format("insert into {0} ({1}) values ({2})", sqlStorageBase.GetValidEntityName(mapping.PersistentAttribute.Entity), fields, parameters);
			return compiledQuery;
		}

		#endregion
	}
}
